// Copyright Epic Games, Inc. All Rights Reserved.

#include <zencore/compress.h>

#include <zencore/blake3.h>
#include <zencore/compositebuffer.h>
#include <zencore/crc32.h>
#include <zencore/endian.h>
#include <zencore/intmath.h>
#include <zencore/iohash.h>
#include <zencore/stream.h>
#include <zencore/testing.h>

#include <oodle2.h>

#include <lz4.h>
#include <functional>
#include <limits>

namespace zen::detail {

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static constexpr uint64_t DefaultBlockSize	= 256 * 1024;
static constexpr uint64_t DefaultHeaderSize = 4 * 1024;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/** Method used to compress the data in a compressed buffer. */
enum class CompressionMethod : uint8_t
{
	/** Header is followed by one uncompressed block. */
	None = 0,
	/** Header is followed by an array of compressed block sizes then the compressed blocks. */
	Oodle = 3,
	/** Header is followed by an array of compressed block sizes then the compressed blocks. */
	LZ4 = 4,
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/** Header used on every compressed buffer. Always stored in big-endian format. */
struct BufferHeader
{
	static constexpr uint32_t ExpectedMagic = 0xb7756362;  // <dot>ucb

	uint32_t		  Magic = ExpectedMagic;  // A magic number to identify a compressed buffer. Always 0xb7756362.
	uint32_t		  Crc32 = 0;			  // A CRC-32 used to check integrity of the buffer. Uses the polynomial 0x04c11db7
	CompressionMethod Method =
		CompressionMethod::None;	   // The method used to compress the buffer. Affects layout of data following the header
	uint8_t	 Compressor			 = 0;  // The method-specific compressor used to compress the buffer.
	uint8_t	 CompressionLevel	 = 0;  // The method-specific compression level used to compress the buffer.
	uint8_t	 BlockSizeExponent	 = 0;  // The power of two size of every uncompressed block except the last. Size is 1 << BlockSizeExponent
	uint32_t BlockCount			 = 0;  // The number of blocks that follow the header
	uint64_t TotalRawSize		 = 0;  // The total size of the uncompressed data
	uint64_t TotalCompressedSize = 0;  // The total size of the compressed data including the header
	BLAKE3	 RawHash;				   // The hash of the uncompressed data

	/** Checks validity of the buffer based on the magic number, method, and CRC-32. */
	static bool IsValid(const CompositeBuffer& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize);
	static bool IsValid(const SharedBuffer& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize)
	{
		return IsValid(CompositeBuffer(CompressedData), OutRawHash, OutRawSize);
	}

	/** Read a header from a buffer that is at least sizeof(BufferHeader) without any validation. */
	static BufferHeader Read(const CompositeBuffer& CompressedData)
	{
		BufferHeader Header;
		if (sizeof(BufferHeader) <= CompressedData.GetSize())
		{
			//			if (CompressedData.GetSegments()[0].AsIoBuffer().IsWholeFile())
			//			{
			//				ZEN_ASSERT(true);
			//			}
			CompositeBuffer::Iterator It;
			CompressedData.CopyTo(MakeMutableMemoryView(&Header, &Header + 1), It);
			Header.ByteSwap();
		}
		return Header;
	}

	/**
	 * Write a header to a memory view that is at least sizeof(BufferHeader).
	 *
	 * @param HeaderView   View of the header to write, including any method-specific header data.
	 */
	void Write(MutableMemoryView HeaderView) const
	{
		BufferHeader Header = *this;
		Header.ByteSwap();
		HeaderView.CopyFrom(MakeMemoryView(&Header, &Header + 1));
		Header.ByteSwap();
		Header.Crc32 = CalculateCrc32(HeaderView);
		Header.ByteSwap();
		HeaderView.CopyFrom(MakeMemoryView(&Header, &Header + 1));
	}

	void ByteSwap()
	{
		Magic				= zen::ByteSwap(Magic);
		Crc32				= zen::ByteSwap(Crc32);
		BlockCount			= zen::ByteSwap(BlockCount);
		TotalRawSize		= zen::ByteSwap(TotalRawSize);
		TotalCompressedSize = zen::ByteSwap(TotalCompressedSize);
	}

	/** Calculate the CRC-32 from a view of a header including any method-specific header data. */
	static uint32_t CalculateCrc32(MemoryView HeaderView)
	{
		uint32_t		   Crc32		= 0;
		constexpr uint64_t MethodOffset = offsetof(BufferHeader, Method);
		for (MemoryView View = HeaderView + MethodOffset; const uint64_t ViewSize = View.GetSize();)
		{
			const int32_t Size = static_cast<int32_t>(zen::Min<uint64_t>(ViewSize, /* INT_MAX */ 2147483647u));
			Crc32			   = zen::MemCrc32(View.GetData(), Size, Crc32);
			View += Size;
		}
		return Crc32;
	}

	bool TryGetCompressParameters(OodleCompressor& OutCompressor, OodleCompressionLevel& OutCompressionLevel, uint64_t& OutBlockSize) const
	{
		switch (Method)
		{
			case CompressionMethod::None:
				OutCompressor		= OodleCompressor::NotSet;
				OutCompressionLevel = OodleCompressionLevel::None;
				OutBlockSize		= 0;
				return true;
			case CompressionMethod::Oodle:
				OutCompressor		= OodleCompressor(Compressor);
				OutCompressionLevel = OodleCompressionLevel(CompressionLevel);
				OutBlockSize		= uint64_t(1) << BlockSizeExponent;
				return true;
			default:
				return false;
		}
	}
};

using FHeader = BufferHeader;

static_assert(sizeof(BufferHeader) == 64, "BufferHeader is the wrong size.");

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class DecoderSource
{
public:
	virtual bool			Read(uint64_t Offset, MutableMemoryView Data) const						  = 0;
	virtual MemoryView		ReadOrView(uint64_t Offset, uint64_t Size, DecoderContext& Context) const = 0;
	virtual CompositeBuffer ReadToComposite(uint64_t Offset, uint64_t Size) const					  = 0;
};

class BaseEncoder
{
public:
	[[nodiscard]] virtual CompositeBuffer Compress(const CompositeBuffer& RawData, uint64_t BlockSize = DefaultBlockSize) const = 0;
};

class BaseDecoder
{
public:
	[[nodiscard]] virtual uint64_t GetHeaderSize(const BufferHeader& Header) const = 0;
	[[nodiscard]] virtual bool	   TryDecompressTo(const BufferHeader&	  Header,
												   const CompositeBuffer& CompressedData,
												   MutableMemoryView	  RawView,
												   uint64_t				  RawOffset) const	   = 0;

	[[nodiscard]] virtual bool TryDecompressTo(DecoderContext&		Context,
											   const DecoderSource& Source,
											   const BufferHeader&	Header,
											   MemoryView			HeaderView,
											   uint64_t				RawOffset,
											   MutableMemoryView	RawView) const = 0;

	[[nodiscard]] virtual CompositeBuffer DecompressToComposite(const BufferHeader&	   Header,
																const CompositeBuffer& CompressedData) const = 0;

	virtual CompositeBuffer DecompressToComposite(DecoderContext&	   Context,
												  const DecoderSource& Source,
												  const FHeader&	   Header,
												  const MemoryView	   HeaderView,
												  uint64_t			   RawOffset,
												  uint64_t			   RawSize) const = 0;
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class NoneEncoder final : public BaseEncoder
{
public:
	[[nodiscard]] CompositeBuffer Compress(const CompositeBuffer& RawData, uint64_t /* BlockSize */) const final
	{
		UniqueBuffer HeaderData = CompressedBuffer::CreateHeaderForNoneEncoder(RawData.GetSize(), BLAKE3::HashBuffer(RawData));
		return CompositeBuffer(HeaderData.MoveToShared(), RawData.MakeOwned());
	}
};

class NoneDecoder final : public BaseDecoder
{
public:
	[[nodiscard]] CompositeBuffer DecompressToComposite(const BufferHeader& Header, const CompositeBuffer& CompressedData) const final
	{
		if (Header.Method == CompressionMethod::None && Header.TotalCompressedSize == CompressedData.GetSize() &&
			Header.TotalCompressedSize == Header.TotalRawSize + sizeof(BufferHeader))
		{
			return CompressedData.Mid(sizeof(BufferHeader), Header.TotalRawSize).MakeOwned();
		}
		return CompositeBuffer();
	}

	CompositeBuffer DecompressToComposite(DecoderContext&	   Context,
										  const DecoderSource& Source,
										  const FHeader&	   Header,
										  const MemoryView	   HeaderView,
										  const uint64_t	   RawOffset,
										  const uint64_t	   RawSize) const final
	{
		ZEN_UNUSED(HeaderView, Context);

		if (Header.Method == CompressionMethod::None && RawOffset <= Header.TotalRawSize && RawSize <= Header.TotalRawSize - RawOffset &&
			Header.TotalCompressedSize == Header.TotalRawSize + sizeof(FHeader))
		{
			return Source.ReadToComposite(sizeof(FHeader) + RawOffset, RawSize);
		}
		return CompositeBuffer();
	}

	[[nodiscard]] bool TryDecompressTo(const BufferHeader&	  Header,
									   const CompositeBuffer& CompressedData,
									   MutableMemoryView	  RawView,
									   uint64_t				  RawOffset) const final
	{
		if (Header.Method == CompressionMethod::None && RawOffset + RawView.GetSize() <= Header.TotalRawSize &&
			Header.TotalCompressedSize == CompressedData.GetSize() &&
			Header.TotalCompressedSize == Header.TotalRawSize + sizeof(BufferHeader))
		{
			CompressedData.CopyTo(RawView, sizeof(BufferHeader) + RawOffset);
			return true;
		}
		return false;
	}

	[[nodiscard]] bool TryDecompressTo(DecoderContext&		Context,
									   const DecoderSource& Source,
									   const BufferHeader&	Header,
									   MemoryView			HeaderView,
									   uint64_t				RawOffset,
									   MutableMemoryView	RawView) const final
	{
		ZEN_UNUSED(HeaderView, Context);

		if (Header.Method == CompressionMethod::None && RawOffset <= Header.TotalRawSize &&
			RawView.GetSize() <= Header.TotalRawSize - RawOffset &&
			Header.TotalCompressedSize == Header.TotalRawSize + sizeof(BufferHeader))
		{
			return Source.Read(sizeof(BufferHeader) + RawOffset, RawView);
		}
		return false;
	}

	[[nodiscard]] uint64_t GetHeaderSize(const BufferHeader&) const final { return sizeof(BufferHeader); }
};

//////////////////////////////////////////////////////////////////////////

class BlockEncoder : public BaseEncoder
{
public:
	CompositeBuffer Compress(const CompositeBuffer& RawData, uint64_t BlockSize = DefaultBlockSize) const final;

protected:
	virtual CompressionMethod GetMethod() const															 = 0;
	virtual uint8_t			  GetCompressor() const														 = 0;
	virtual uint8_t			  GetCompressionLevel() const												 = 0;
	virtual uint64_t		  CompressBlockBound(uint64_t RawSize) const								 = 0;
	virtual bool			  CompressBlock(MutableMemoryView& CompressedData, MemoryView RawData) const = 0;

private:
	uint64_t GetCompressedBlocksBound(uint64_t BlockCount, uint64_t BlockSize, uint64_t RawSize) const
	{
		switch (BlockCount)
		{
			case 0:
				return 0;
			case 1:
				return CompressBlockBound(RawSize);
			default:
				return CompressBlockBound(BlockSize) - BlockSize + RawSize;
		}
	}
};

CompositeBuffer
BlockEncoder::Compress(const CompositeBuffer& RawData, const uint64_t BlockSize) const
{
	ZEN_ASSERT(IsPow2(BlockSize) && (BlockSize <= (1u << 31)));

	const uint64_t RawSize = RawData.GetSize();
	BLAKE3Stream   RawHash;

	const uint64_t BlockCount = RoundUp(RawSize, BlockSize) / BlockSize;
	ZEN_ASSERT(BlockCount <= ~uint32_t(0));

	// Allocate the buffer for the header, metadata, and compressed blocks.
	const uint64_t MetaSize			  = BlockCount * sizeof(uint32_t);
	const uint64_t CompressedDataSize = sizeof(BufferHeader) + MetaSize + GetCompressedBlocksBound(BlockCount, BlockSize, RawSize);
	UniqueBuffer   CompressedData	  = UniqueBuffer::Alloc(CompressedDataSize);

	// Compress the raw data in blocks and store the raw data for incompressible blocks.
	std::vector<uint32_t> CompressedBlockSizes;
	CompressedBlockSizes.reserve(BlockCount);
	uint64_t CompressedSize = 0;
	{
		UniqueBuffer	  RawBlockCopy;
		MutableMemoryView CompressedBlocksView = CompressedData.GetMutableView() + sizeof(BufferHeader) + MetaSize;

		CompositeBuffer::Iterator It = RawData.GetIterator(0);

		for (uint64_t RawOffset = 0; RawOffset < RawSize;)
		{
			const uint64_t	 RawBlockSize = zen::Min(RawSize - RawOffset, BlockSize);
			const MemoryView RawBlock	  = RawData.ViewOrCopyRange(It, RawBlockSize, RawBlockCopy);
			RawHash.Append(RawBlock);

			MutableMemoryView CompressedBlock = CompressedBlocksView;
			if (!CompressBlock(CompressedBlock, RawBlock))
			{
				return CompositeBuffer();
			}

			uint64_t CompressedBlockSize = CompressedBlock.GetSize();
			if (RawBlockSize <= CompressedBlockSize)
			{
				CompressedBlockSize	 = RawBlockSize;
				CompressedBlocksView = CompressedBlocksView.CopyFrom(RawBlock);
			}
			else
			{
				CompressedBlocksView += CompressedBlockSize;
			}

			CompressedBlockSizes.push_back(static_cast<uint32_t>(CompressedBlockSize));
			CompressedSize += CompressedBlockSize;
			RawOffset += RawBlockSize;
		}
	}

	// Return an uncompressed buffer if the compressed data is larger than the raw data.
	if (RawSize <= MetaSize + CompressedSize)
	{
		CompressedData.Reset();
		return NoneEncoder().Compress(RawData, BlockSize);
	}

	// Write the header and calculate the CRC-32.
	for (uint32_t& Size : CompressedBlockSizes)
	{
		Size = ByteSwap(Size);
	}
	CompressedData.GetMutableView().Mid(sizeof(BufferHeader), MetaSize).CopyFrom(MakeMemoryView(CompressedBlockSizes));

	BufferHeader Header;
	Header.Method			   = GetMethod();
	Header.Compressor		   = GetCompressor();
	Header.CompressionLevel	   = GetCompressionLevel();
	Header.BlockSizeExponent   = static_cast<uint8_t>(zen::FloorLog2_64(BlockSize));
	Header.BlockCount		   = static_cast<uint32_t>(BlockCount);
	Header.TotalRawSize		   = RawSize;
	Header.TotalCompressedSize = sizeof(BufferHeader) + MetaSize + CompressedSize;
	Header.RawHash			   = RawHash.GetHash();
	Header.Write(CompressedData.GetMutableView().Left(sizeof(BufferHeader) + MetaSize));

	const MemoryView CompositeView = CompressedData.GetView().Left(Header.TotalCompressedSize);
	return CompositeBuffer(SharedBuffer::MakeView(CompositeView, CompressedData.MoveToShared()));
}

class BlockDecoder : public BaseDecoder
{
public:
	[[nodiscard]] uint64_t GetHeaderSize(const BufferHeader& Header) const final
	{
		return sizeof(BufferHeader) + sizeof(uint32_t) * uint64_t(Header.BlockCount);
	}

	[[nodiscard]] virtual bool TryDecompressTo(DecoderContext&		Context,
											   const DecoderSource& Source,
											   const BufferHeader&	Header,
											   MemoryView			HeaderView,
											   uint64_t				RawOffset,
											   MutableMemoryView	RawView) const final;

	CompositeBuffer DecompressToComposite(const BufferHeader& Header, const CompositeBuffer& CompressedData) const final;

	CompositeBuffer DecompressToComposite(DecoderContext&	   Context,
										  const DecoderSource& Source,
										  const FHeader&	   Header,
										  const MemoryView	   HeaderView,
										  const uint64_t	   RawOffset,
										  const uint64_t	   RawSize) const final;

	[[nodiscard]] bool TryDecompressTo(const BufferHeader&	  Header,
									   const CompositeBuffer& CompressedData,
									   MutableMemoryView	  RawView,
									   uint64_t				  RawOffset) const final;

protected:
	virtual bool DecompressBlock(MutableMemoryView RawData, MemoryView CompressedData) const = 0;
};

CompositeBuffer
BlockDecoder::DecompressToComposite(const BufferHeader& Header, const CompositeBuffer& CompressedData) const
{
	if (Header.BlockCount == 0 || Header.TotalCompressedSize != CompressedData.GetSize())
	{
		return CompositeBuffer();
	}

	// The raw data cannot reference the compressed data unless it is owned.
	// An empty raw buffer requires an empty segment, which this path creates.
	if (!CompressedData.IsOwned() || Header.TotalRawSize == 0)
	{
		UniqueBuffer Buffer = UniqueBuffer::Alloc(Header.TotalRawSize);
		return TryDecompressTo(Header, CompressedData, Buffer, 0) ? CompositeBuffer(Buffer.MoveToShared()) : CompositeBuffer();
	}

	std::vector<uint32_t> CompressedBlockSizes;
	CompressedBlockSizes.resize(Header.BlockCount);
	CompressedData.CopyTo(MakeMutableMemoryView(CompressedBlockSizes), sizeof(BufferHeader));

	for (uint32_t& Size : CompressedBlockSizes)
	{
		Size = ByteSwap(Size);
	}

	// Allocate the buffer for the raw blocks that were compressed.
	SharedBuffer	  RawData;
	MutableMemoryView RawDataView;
	const uint64_t	  BlockSize = uint64_t(1) << Header.BlockSizeExponent;
	{
		uint64_t RawDataSize	  = 0;
		uint64_t RemainingRawSize = Header.TotalRawSize;
		for (const uint32_t CompressedBlockSize : CompressedBlockSizes)
		{
			const uint64_t RawBlockSize = zen::Min(RemainingRawSize, BlockSize);
			if (CompressedBlockSize < BlockSize)
			{
				RawDataSize += RawBlockSize;
			}
			RemainingRawSize -= RawBlockSize;
		}
		UniqueBuffer RawDataBuffer = UniqueBuffer::Alloc(RawDataSize);
		RawDataView				   = RawDataBuffer;
		RawData					   = RawDataBuffer.MoveToShared();
	}

	// Decompress the compressed data in blocks and reference the uncompressed blocks.
	uint64_t				  PendingCompressedSegmentOffset = sizeof(BufferHeader) + uint64_t(Header.BlockCount) * sizeof(uint32_t);
	uint64_t				  PendingCompressedSegmentSize	 = 0;
	uint64_t				  PendingRawSegmentOffset		 = 0;
	uint64_t				  PendingRawSegmentSize			 = 0;
	std::vector<SharedBuffer> Segments;

	const auto CommitPendingCompressedSegment =
		[&PendingCompressedSegmentOffset, &PendingCompressedSegmentSize, &CompressedData, &Segments] {
			if (PendingCompressedSegmentSize)
			{
				CompressedData.IterateRange(PendingCompressedSegmentOffset,
											PendingCompressedSegmentSize,
											[&Segments](MemoryView View, const SharedBuffer& ViewOuter) {
												Segments.push_back(SharedBuffer::MakeView(View, ViewOuter));
											});
				PendingCompressedSegmentOffset += PendingCompressedSegmentSize;
				PendingCompressedSegmentSize = 0;
			}
		};

	const auto CommitPendingRawSegment = [&PendingRawSegmentOffset, &PendingRawSegmentSize, &RawData, &Segments] {
		if (PendingRawSegmentSize)
		{
			const MemoryView PendingSegment = RawData.GetView().Mid(PendingRawSegmentOffset, PendingRawSegmentSize);
			Segments.push_back(SharedBuffer::MakeView(PendingSegment, RawData));
			PendingRawSegmentOffset += PendingRawSegmentSize;
			PendingRawSegmentSize = 0;
		}
	};

	UniqueBuffer CompressedBlockCopy;
	uint64_t	 RemainingRawSize		 = Header.TotalRawSize;
	uint64_t	 RemainingCompressedSize = CompressedData.GetSize();
	for (const uint32_t CompressedBlockSize : CompressedBlockSizes)
	{
		if (RemainingCompressedSize < CompressedBlockSize)
		{
			return CompositeBuffer();
		}

		const uint64_t RawBlockSize = zen::Min(RemainingRawSize, BlockSize);
		if (RawBlockSize == CompressedBlockSize)
		{
			CommitPendingRawSegment();
			PendingCompressedSegmentSize += RawBlockSize;
		}
		else
		{
			CommitPendingCompressedSegment();
			const MemoryView CompressedBlock =
				CompressedData.ViewOrCopyRange(PendingCompressedSegmentOffset, CompressedBlockSize, CompressedBlockCopy);
			if (!DecompressBlock(RawDataView.Left(RawBlockSize), CompressedBlock))
			{
				return CompositeBuffer();
			}
			PendingCompressedSegmentOffset += CompressedBlockSize;
			PendingRawSegmentSize += RawBlockSize;
			RawDataView += RawBlockSize;
		}

		RemainingCompressedSize -= CompressedBlockSize;
		RemainingRawSize -= RawBlockSize;
	}

	CommitPendingCompressedSegment();
	CommitPendingRawSegment();

	return CompositeBuffer(std::move(Segments));
}

bool
BlockDecoder::TryDecompressTo(const BufferHeader&	 Header,
							  const CompositeBuffer& CompressedData,
							  MutableMemoryView		 RawView,
							  uint64_t				 RawOffset) const
{
	if (Header.TotalRawSize < RawOffset + RawView.GetSize() || Header.TotalCompressedSize != CompressedData.GetSize())
	{
		return false;
	}

	const uint64_t BlockSize = uint64_t(1) << Header.BlockSizeExponent;

	UniqueBuffer BlockSizeBuffer;
	MemoryView BlockSizeView = CompressedData.ViewOrCopyRange(sizeof(BufferHeader), Header.BlockCount * sizeof(uint32_t), BlockSizeBuffer);
	std::span<uint32_t const> CompressedBlockSizes(reinterpret_cast<const uint32_t*>(BlockSizeView.GetData()), Header.BlockCount);

	UniqueBuffer CompressedBlockCopy;
	UniqueBuffer UncompressedBlockCopy;

	const size_t   FirstBlockIndex	  = uint64_t(RawOffset / BlockSize);
	const size_t   LastBlockIndex	  = uint64_t((RawOffset + RawView.GetSize() - 1) / BlockSize);
	const uint64_t LastBlockSize	  = BlockSize - ((Header.BlockCount * BlockSize) - Header.TotalRawSize);
	uint64_t	   OffsetInFirstBlock = RawOffset % BlockSize;
	uint64_t	   CompressedOffset	  = sizeof(BufferHeader) + uint64_t(Header.BlockCount) * sizeof(uint32_t);
	uint64_t	   RemainingRawSize	  = RawView.GetSize();

	for (size_t BlockIndex = 0; BlockIndex < FirstBlockIndex; BlockIndex++)
	{
		const uint32_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]);
		CompressedOffset += CompressedBlockSize;
	}

	for (size_t BlockIndex = FirstBlockIndex; BlockIndex <= LastBlockIndex; BlockIndex++)
	{
		const uint64_t UncompressedBlockSize = BlockIndex == Header.BlockCount - 1 ? LastBlockSize : BlockSize;
		const uint32_t CompressedBlockSize	 = ByteSwap(CompressedBlockSizes[BlockIndex]);
		const bool	   IsCompressed			 = CompressedBlockSize < UncompressedBlockSize;

		const uint64_t BytesToUncompress = OffsetInFirstBlock > 0 ? zen::Min(RawView.GetSize(), UncompressedBlockSize - OffsetInFirstBlock)
																  : zen::Min(RemainingRawSize, BlockSize);

		MemoryView CompressedBlock = CompressedData.ViewOrCopyRange(CompressedOffset, CompressedBlockSize, CompressedBlockCopy);

		if (IsCompressed)
		{
			MutableMemoryView UncompressedBlock = RawView.Left(BytesToUncompress);

			const bool IsAligned = BytesToUncompress == UncompressedBlockSize;
			if (!IsAligned)
			{
				// Decompress to a temporary buffer when the first or the last block reads are not aligned with the block boundaries.
				if (UncompressedBlockCopy.IsNull())
				{
					UncompressedBlockCopy = UniqueBuffer::Alloc(BlockSize);
				}
				UncompressedBlock = UncompressedBlockCopy.GetMutableView().Mid(0, UncompressedBlockSize);
			}

			if (!DecompressBlock(UncompressedBlock, CompressedBlock))
			{
				return false;
			}

			if (!IsAligned)
			{
				RawView.CopyFrom(UncompressedBlock.Mid(OffsetInFirstBlock, BytesToUncompress));
			}
		}
		else
		{
			RawView.CopyFrom(CompressedBlock.Mid(OffsetInFirstBlock, BytesToUncompress));
		}

		OffsetInFirstBlock = 0;
		RemainingRawSize -= BytesToUncompress;
		CompressedOffset += CompressedBlockSize;
		RawView += BytesToUncompress;
	}

	return RemainingRawSize == 0;
}

bool
BlockDecoder::TryDecompressTo(DecoderContext&	   Context,
							  const DecoderSource& Source,
							  const FHeader&	   Header,
							  const MemoryView	   HeaderView,
							  const uint64_t	   RawOffset,
							  MutableMemoryView	   RawView) const
{
	if (Header.TotalRawSize < RawOffset + RawView.GetSize())
	{
		return false;
	}

	const uint64_t BlockSize	   = uint64_t(1) << Header.BlockSizeExponent;
	const uint64_t LastBlockSize   = BlockSize - (BlockSize * Header.BlockCount - Header.TotalRawSize);
	const uint32_t FirstBlockIndex = uint32_t(RawOffset / BlockSize);
	const uint32_t LastBlockIndex  = uint32_t((RawOffset + RawView.GetSize() - 1) / BlockSize);

	uint64_t RawBlockOffset = RawOffset % BlockSize;

	const uint32_t* const CompressedBlockSizes = static_cast<const uint32_t*>(HeaderView.RightChop(sizeof(FHeader)).GetData());
	uint64_t			  CompressedOffset	   = sizeof(FHeader) + sizeof(uint32_t) * uint32_t(Header.BlockCount);

	// + Algo::TransformAccumulate(MakeArrayView(CompressedBlockSizes, FirstBlockIndex),
	//					[](uint32_t Size) -> uint64_t { return NETWORK_ORDER32(Size); },
	//					uint64_t(0)

	for (uint32_t i = 0; i < FirstBlockIndex; ++i)
	{
		CompressedOffset += ByteSwap(CompressedBlockSizes[i]);
	}

	for (uint32_t BlockIndex = FirstBlockIndex; BlockIndex <= LastBlockIndex; BlockIndex++)
	{
		const uint64_t RawBlockSize		   = BlockIndex == Header.BlockCount - 1 ? LastBlockSize : BlockSize;
		const uint64_t RawBlockReadSize	   = zen::Min(RawView.GetSize(), RawBlockSize - RawBlockOffset);
		const uint32_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]);
		const bool	   bIsCompressed	   = CompressedBlockSize < RawBlockSize;

		if (bIsCompressed)
		{
			if (Context.RawBlockIndex == BlockIndex)
			{
				RawView.Left(RawBlockReadSize).CopyFrom(Context.RawBlock.GetView().Mid(RawBlockOffset, RawBlockReadSize));
			}
			else
			{
				MutableMemoryView RawBlock;
				if (RawBlockReadSize == RawBlockSize)
				{
					RawBlock = RawView.Left(RawBlockSize);
				}
				else
				{
					if (Context.RawBlock.GetSize() < RawBlockSize)
					{
						Context.RawBlock = UniqueBuffer::Alloc(BlockSize);
					}
					RawBlock			  = Context.RawBlock.GetMutableView().Left(RawBlockSize);
					Context.RawBlockIndex = BlockIndex;
				}

				const MemoryView CompressedBlock = Source.ReadOrView(CompressedOffset, CompressedBlockSize, Context);
				if (CompressedBlock.IsEmpty() || !DecompressBlock(RawBlock, CompressedBlock))
				{
					return false;
				}

				if (RawBlockReadSize != RawBlockSize)
				{
					RawView.CopyFrom(RawBlock.Mid(RawBlockOffset, RawBlockReadSize));
				}
			}
		}
		else
		{
			Source.Read(CompressedOffset + RawBlockOffset, RawView.Left(RawBlockReadSize));
		}

		RawBlockOffset = 0;
		CompressedOffset += CompressedBlockSize;
		RawView += RawBlockReadSize;
	}

	return RawView.GetSize() == 0;
}

CompositeBuffer
BlockDecoder::DecompressToComposite(DecoderContext&		 Context,
									const DecoderSource& Source,
									const FHeader&		 Header,
									const MemoryView	 HeaderView,
									const uint64_t		 RawOffset,
									const uint64_t		 RawSize) const
{
	if (RawSize > 0)
	{
		UniqueBuffer Buffer = UniqueBuffer::Alloc(RawSize);
		if (TryDecompressTo(Context, Source, Header, HeaderView, RawOffset, Buffer))
		{
			return CompositeBuffer(Buffer.MoveToShared());
		}
	}
	return CompositeBuffer();
}

//////////////////////////////////////////////////////////////////////////

struct OodleInit
{
	OodleInit()
	{
		OodleConfigValues Config;
		Oodle_GetConfigValues(&Config);
		// Always read/write Oodle v9 binary data.
		Config.m_OodleLZ_BackwardsCompatible_MajorVersion = 9;
		Oodle_SetConfigValues(&Config);
	}
};

OodleInit InitOodle;

class OodleEncoder final : public BlockEncoder
{
public:
	OodleEncoder(OodleCompressor InCompressor, OodleCompressionLevel InCompressionLevel)
	: Compressor(InCompressor)
	, CompressionLevel(InCompressionLevel)
	{
	}

protected:
	CompressionMethod GetMethod() const final { return CompressionMethod::Oodle; }
	uint8_t			  GetCompressor() const final { return static_cast<uint8_t>(Compressor); }
	uint8_t			  GetCompressionLevel() const final { return static_cast<uint8_t>(CompressionLevel); }

	uint64_t CompressBlockBound(uint64_t RawSize) const final
	{
		return static_cast<uint64_t>(OodleLZ_GetCompressedBufferSizeNeeded(OodleLZ_Compressor_Kraken, static_cast<OO_SINTa>(RawSize)));
	}

	bool CompressBlock(MutableMemoryView& CompressedData, MemoryView RawData) const final
	{
		const OodleLZ_Compressor	   LZCompressor		  = GetOodleLZCompressor(Compressor);
		const OodleLZ_CompressionLevel LZCompressionLevel = GetOodleLZCompressionLevel(CompressionLevel);
		if (LZCompressor == OodleLZ_Compressor_Invalid || LZCompressionLevel == OodleLZ_CompressionLevel_Invalid ||
			LZCompressionLevel == OodleLZ_CompressionLevel_None)
		{
			return false;
		}

		const OO_SINTa RawSize = static_cast<OO_SINTa>(RawData.GetSize());
		if (static_cast<OO_SINTa>(CompressedData.GetSize()) < OodleLZ_GetCompressedBufferSizeNeeded(LZCompressor, RawSize))
		{
			return false;
		}

		const OO_SINTa Size = OodleLZ_Compress(LZCompressor, RawData.GetData(), RawSize, CompressedData.GetData(), LZCompressionLevel);
		CompressedData.LeftInline(static_cast<uint64_t>(Size));
		return Size > 0;
	}

	static OodleLZ_Compressor GetOodleLZCompressor(OodleCompressor Compressor)
	{
		switch (Compressor)
		{
			case OodleCompressor::Selkie:
				return OodleLZ_Compressor_Selkie;
			case OodleCompressor::Mermaid:
				return OodleLZ_Compressor_Mermaid;
			case OodleCompressor::Kraken:
				return OodleLZ_Compressor_Kraken;
			case OodleCompressor::Leviathan:
				return OodleLZ_Compressor_Leviathan;
			case OodleCompressor::NotSet:
			default:
				return OodleLZ_Compressor_Invalid;
		}
	}

	static OodleLZ_CompressionLevel GetOodleLZCompressionLevel(OodleCompressionLevel Level)
	{
		const int IntLevel = (int)Level;
		if (IntLevel < (int)OodleLZ_CompressionLevel_Min || IntLevel > (int)OodleLZ_CompressionLevel_Max)
		{
			return OodleLZ_CompressionLevel_Invalid;
		}
		return OodleLZ_CompressionLevel(IntLevel);
	}

private:
	const OodleCompressor		Compressor;
	const OodleCompressionLevel CompressionLevel;
};

class OodleDecoder final : public BlockDecoder
{
protected:
	bool DecompressBlock(MutableMemoryView RawData, MemoryView CompressedData) const final
	{
		const OO_SINTa RawSize = static_cast<OO_SINTa>(RawData.GetSize());
		const OO_SINTa Size	   = OodleLZ_Decompress(CompressedData.GetData(),
													static_cast<OO_SINTa>(CompressedData.GetSize()),
													RawData.GetData(),
													RawSize,
													OodleLZ_FuzzSafe_Yes,
													OodleLZ_CheckCRC_Yes,
													OodleLZ_Verbosity_None);
		return Size == RawSize;
	}
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class LZ4Decoder final : public BlockDecoder
{
protected:
	bool DecompressBlock(MutableMemoryView RawData, MemoryView CompressedData) const final
	{
		if (CompressedData.GetSize() <= std::numeric_limits<int>::max())
		{
			const int Size = LZ4_decompress_safe(static_cast<const char*>(CompressedData.GetData()),
												 static_cast<char*>(RawData.GetData()),
												 static_cast<int>(CompressedData.GetSize()),
												 static_cast<int>(zen::Min<uint64_t>(RawData.GetSize(), uint64_t(LZ4_MAX_INPUT_SIZE))));
			return static_cast<uint64_t>(Size) == RawData.GetSize();
		}
		return false;
	}
};

//////////////////////////////////////////////////////////////////////////

static const BaseDecoder*
GetDecoder(CompressionMethod Method)
{
	static NoneDecoder	None;
	static OodleDecoder Oodle;
	static LZ4Decoder	LZ4;

	switch (Method)
	{
		default:
			return nullptr;
		case CompressionMethod::None:
			return &None;
		case CompressionMethod::Oodle:
			return &Oodle;
		case CompressionMethod::LZ4:
			return &LZ4;
	}
}

//////////////////////////////////////////////////////////////////////////

bool
ReadHeader(const CompositeBuffer& CompressedData, BufferHeader& OutHeader, UniqueBuffer* OutHeaderData)
{
	const uint64_t CompressedDataSize = CompressedData.GetSize();
	if (CompressedDataSize < sizeof(BufferHeader))
	{
		return false;
	}

	const size_t HeaderBufferSize = 1024;
	uint8_t		 HeaderBuffer[HeaderBufferSize];
	uint64_t	 ReadSize		  = Min(CompressedDataSize, HeaderBufferSize);
	uint64_t	 FirstSegmentSize = CompressedData.GetSegments()[0].GetSize();
	if (FirstSegmentSize >= sizeof(BufferHeader))
	{
		// Keep first read inside first segment if possible
		ReadSize = Min(ReadSize, FirstSegmentSize);
	}

	MutableMemoryView		  HeaderMemory(HeaderBuffer, &HeaderBuffer[ReadSize]);
	CompositeBuffer::Iterator It = CompressedData.GetIterator(0);
	CompressedData.CopyTo(HeaderMemory, It);

	OutHeader = *reinterpret_cast<BufferHeader*>(HeaderMemory.GetData());
	OutHeader.ByteSwap();
	if (OutHeader.Magic != BufferHeader::ExpectedMagic)
	{
		return false;
	}
	if (OutHeader.TotalCompressedSize > CompressedDataSize)
	{
		return false;
	}
	const BaseDecoder* const Decoder = GetDecoder(OutHeader.Method);
	if (!Decoder)
	{
		return false;
	}
	uint64_t FullHeaderSize = Decoder->GetHeaderSize(OutHeader);
	if (FullHeaderSize > CompressedDataSize)
	{
		return false;
	}
	if (OutHeaderData)
	{
		*OutHeaderData						  = UniqueBuffer::Alloc(FullHeaderSize);
		MutableMemoryView RemainingHeaderView = OutHeaderData->GetMutableView().CopyFrom(HeaderMemory.Mid(0, FullHeaderSize));
		if (!RemainingHeaderView.IsEmpty())
		{
			CompressedData.CopyTo(RemainingHeaderView, It);
		}
		if (OutHeader.Crc32 != BufferHeader::CalculateCrc32(OutHeaderData->GetView()))
		{
			return false;
		}
	}
	else if (FullHeaderSize < ReadSize)
	{
		if (OutHeader.Crc32 != BufferHeader::CalculateCrc32(HeaderMemory.Mid(0, FullHeaderSize)))
		{
			return false;
		}
	}
	else
	{
		UniqueBuffer	  HeaderData		  = UniqueBuffer::Alloc(FullHeaderSize);
		MutableMemoryView RemainingHeaderView = HeaderData.GetMutableView().CopyFrom(HeaderMemory.Mid(0, FullHeaderSize));
		if (!RemainingHeaderView.IsEmpty())
		{
			CompressedData.CopyTo(RemainingHeaderView, It);
		}
		if (OutHeader.Crc32 != BufferHeader::CalculateCrc32(HeaderData.GetView()))
		{
			return false;
		}
	}
	return true;
}

bool
BufferHeader::IsValid(const CompositeBuffer& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize)
{
	detail::BufferHeader Header;
	if (ReadHeader(CompressedData, Header, nullptr))
	{
		OutRawHash = IoHash::FromBLAKE3(Header.RawHash);
		OutRawSize = Header.TotalRawSize;
		return true;
	}
	return false;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static bool
TryReadHeader(DecoderContext& Context, Archive& Ar, FHeader& OutHeader, MemoryView& OutHeaderView)
{
	if (Context.HeaderOffset != MAX_uint64)
	{
		OutHeaderView = Context.Header.GetView().Left(Context.HeaderSize);
		MakeMutableMemoryView(&OutHeader, &OutHeader + 1).CopyFrom(OutHeaderView.Left(sizeof(FHeader)));
		OutHeader.ByteSwap();
		return true;
	}

	const int64_t Offset = Ar.Tell();

	FHeader& Header = OutHeader;
	Ar.Serialize(&Header, sizeof(FHeader));
	Header.ByteSwap();

	if (const BaseDecoder* const Decoder = GetDecoder(Header.Method); Decoder && Header.Magic == FHeader::ExpectedMagic)
	{
		const uint64_t HeaderSize = Decoder->GetHeaderSize(Header);
		if (Context.Header.GetSize() < HeaderSize)
		{
			Context.Header = UniqueBuffer::Alloc(zen::Max(NextPow2(HeaderSize), DefaultHeaderSize));
		}

		const MutableMemoryView HeaderView = Context.Header.GetMutableView().Left(HeaderSize);
		const MutableMemoryView HeaderTail = HeaderView.CopyFrom(MakeMemoryView(&Header, &Header + 1));
		Ar.Serialize(HeaderTail.GetData(), int64_t(HeaderTail.GetSize()));

		FHeader* const HeaderCopy = static_cast<FHeader*>(HeaderView.GetData());
		HeaderCopy->ByteSwap();
		if (Header.Crc32 == FHeader::CalculateCrc32(HeaderView))
		{
			Context.HeaderOffset  = uint64_t(Offset);
			Context.HeaderSize	  = HeaderSize;
			Context.HeaderCrc32	  = Header.Crc32;
			Context.RawBlockIndex = MAX_uint32;
			OutHeaderView		  = HeaderView;
			return true;
		}
	}

	return false;
}

static bool
TryReadHeader(DecoderContext& Context, const CompositeBuffer& Buffer, FHeader& OutHeader, MemoryView& OutHeaderView)
{
	if (Context.HeaderOffset != MAX_uint64)
	{
		OutHeaderView = Buffer.ViewOrCopyRange(Context.HeaderOffset, Context.HeaderSize, Context.Header);
		MakeMutableMemoryView(&OutHeader, &OutHeader + 1).CopyFrom(OutHeaderView.Left(sizeof(FHeader)));
		OutHeader.ByteSwap();
		return true;
	}

	if (Buffer.GetSize() < sizeof(FHeader))
	{
		return false;
	}

	FHeader& Header = OutHeader;
	Buffer.CopyTo(MakeMutableMemoryView(&Header, &Header + 1));
	Header.ByteSwap();

	if (const BaseDecoder* const Decoder = GetDecoder(Header.Method); Decoder && Header.Magic == FHeader::ExpectedMagic)
	{
		const uint64_t	 HeaderSize = Decoder->GetHeaderSize(Header);
		const MemoryView HeaderView = Buffer.ViewOrCopyRange(0, HeaderSize, Context.Header, [](uint64_t Size) {
			return UniqueBuffer::Alloc(zen::Max(NextPow2(Size), DefaultHeaderSize));
		});
		if (Header.Crc32 == FHeader::CalculateCrc32(HeaderView))
		{
			Context.HeaderOffset = 0;
			Context.HeaderSize	 = HeaderSize;
			if (Context.HeaderCrc32 != Header.Crc32)
			{
				Context.HeaderCrc32	  = Header.Crc32;
				Context.RawBlockIndex = MAX_uint32;
			}
			OutHeaderView = HeaderView;
			return true;
		}
	}

	return false;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class ArchiveDecoderSource final : public DecoderSource
{
public:
	explicit ArchiveDecoderSource(Archive& InArchive, const uint64_t InBaseOffset) : m_Archive(InArchive), m_BaseOffset(InBaseOffset) {}

	bool Read(uint64_t Offset, MutableMemoryView Data) const final
	{
		m_Archive.Seek(int64_t(m_BaseOffset + Offset));
		m_Archive.Serialize(Data.GetData(), int64_t(Data.GetSize()));
		return !m_Archive.IsError();
	}

	MemoryView ReadOrView(const uint64_t Offset, const uint64_t Size, DecoderContext& Context) const final
	{
		if (Context.CompressedBlock.GetSize() < Size)
		{
			Context.CompressedBlock = UniqueBuffer::Alloc(zen::Max(NextPow2(Size), DefaultBlockSize));
		}
		const MutableMemoryView View = Context.CompressedBlock.GetMutableView().Left(Size);
		return Read(Offset, View) ? View : MemoryView();
	}

	CompositeBuffer ReadToComposite(const uint64_t Offset, const uint64_t Size) const final
	{
		UniqueBuffer Buffer = UniqueBuffer::Alloc(Size);
		if (Read(Offset, Buffer))
		{
			return CompositeBuffer(Buffer.MoveToShared());
		}
		return CompositeBuffer();
	}

private:
	Archive&	   m_Archive;
	const uint64_t m_BaseOffset;
};

class BufferDecoderSource final : public DecoderSource
{
public:
	explicit BufferDecoderSource(const CompositeBuffer& InBuffer) : m_Buffer(InBuffer) {}

	bool Read(const uint64_t Offset, const MutableMemoryView Data) const final
	{
		if (Offset + Data.GetSize() <= m_Buffer.GetSize())
		{
			m_Buffer.CopyTo(Data, Offset);
			return true;
		}
		return false;
	}

	MemoryView ReadOrView(const uint64_t Offset, const uint64_t Size, DecoderContext& Context) const final
	{
		return m_Buffer.ViewOrCopyRange(Offset, Size, Context.CompressedBlock, [](uint64_t BufferSize) -> UniqueBuffer {
			return UniqueBuffer::Alloc(zen::Max(zen::NextPow2(BufferSize), DefaultBlockSize));
		});
	}

	CompositeBuffer ReadToComposite(const uint64_t Offset, const uint64_t Size) const final
	{
		return m_Buffer.Mid(Offset, Size).MakeOwned();
	}

private:
	const CompositeBuffer& m_Buffer;
};

//////////////////////////////////////////////////////////////////////////

template<typename BufferType>
inline CompositeBuffer
ValidBufferOrEmpty(BufferType&& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize)
{
	return BufferHeader::IsValid(CompressedData, OutRawHash, OutRawSize) ? CompositeBuffer(std::forward<BufferType>(CompressedData))
																		 : CompositeBuffer();
}

CompositeBuffer
GetCompressedRange(const BufferHeader&	  Header,
				   MemoryView			  HeaderRawData,
				   const CompositeBuffer& CompressedData,
				   uint64_t				  RawOffset,
				   uint64_t				  RawSize)
{
	if (Header.TotalRawSize < RawOffset + RawSize)
	{
		return CompositeBuffer();
	}
	if (Header.Method == CompressionMethod::None)
	{
		BufferHeader NewHeader		  = Header;
		NewHeader.Crc32				  = 0;
		NewHeader.TotalRawSize		  = RawSize;
		NewHeader.TotalCompressedSize = NewHeader.TotalRawSize + sizeof(BufferHeader);
		NewHeader.RawHash			  = BLAKE3();

		UniqueBuffer HeaderData = UniqueBuffer::Alloc(sizeof(BufferHeader));
		NewHeader.Write(HeaderData);

		return CompositeBuffer(HeaderData.MoveToShared(), CompressedData.Mid(sizeof(BufferHeader) + RawOffset, RawSize).MakeOwned());
	}
	else
	{
		MemoryView				  BlockSizeView = HeaderRawData.Mid(sizeof(Header), Header.BlockCount * sizeof(uint32_t));
		std::span<uint32_t const> CompressedBlockSizes(reinterpret_cast<const uint32_t*>(BlockSizeView.GetData()), Header.BlockCount);

		const uint64_t BlockSize		= uint64_t(1) << Header.BlockSizeExponent;
		const uint64_t LastBlockSize	= BlockSize - ((Header.BlockCount * BlockSize) - Header.TotalRawSize);
		const size_t   FirstBlock		= uint64_t(RawOffset / BlockSize);
		const size_t   LastBlock		= uint64_t((RawOffset + RawSize - 1) / BlockSize);
		uint64_t	   CompressedOffset = sizeof(BufferHeader) + uint64_t(Header.BlockCount) * sizeof(uint32_t);

		const uint64_t		  NewBlockCount		= LastBlock - FirstBlock + 1;
		const uint64_t		  NewMetaSize		= NewBlockCount * sizeof(uint32_t);
		uint64_t			  NewCompressedSize = 0;
		uint64_t			  NewTotalRawSize	= 0;
		std::vector<uint32_t> NewCompressedBlockSizes;

		NewCompressedBlockSizes.reserve(NewBlockCount);
		for (size_t BlockIndex = FirstBlock; BlockIndex <= LastBlock; ++BlockIndex)
		{
			const uint64_t UncompressedBlockSize = (BlockIndex == Header.BlockCount - 1) ? LastBlockSize : BlockSize;
			NewTotalRawSize += UncompressedBlockSize;

			const uint32_t CompressedBlockSize = CompressedBlockSizes[BlockIndex];
			NewCompressedBlockSizes.push_back(CompressedBlockSize);
			NewCompressedSize += ByteSwap(CompressedBlockSize);
		}

		const uint64_t NewTotalCompressedSize  = sizeof(BufferHeader) + NewBlockCount * sizeof(uint32_t) + NewCompressedSize;
		const uint64_t NewCompressedHeaderSize = sizeof(BufferHeader) + NewBlockCount * sizeof(uint32_t);
		UniqueBuffer   NewCompressedHeaderData = UniqueBuffer::Alloc(NewCompressedHeaderSize);

		// Seek to first compressed block
		for (size_t BlockIndex = 0; BlockIndex < FirstBlock; ++BlockIndex)
		{
			const uint64_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]);
			CompressedOffset += CompressedBlockSize;
		}

		CompositeBuffer NewCompressedData = CompressedData.Mid(CompressedOffset, NewCompressedSize).MakeOwned();

		// Copy block sizes
		NewCompressedHeaderData.GetMutableView().Mid(sizeof(BufferHeader), NewMetaSize).CopyFrom(MakeMemoryView(NewCompressedBlockSizes));

		BufferHeader NewHeader;
		NewHeader.Crc32				  = 0;
		NewHeader.Method			  = Header.Method;
		NewHeader.Compressor		  = Header.Compressor;
		NewHeader.CompressionLevel	  = Header.CompressionLevel;
		NewHeader.BlockSizeExponent	  = Header.BlockSizeExponent;
		NewHeader.BlockCount		  = static_cast<uint32_t>(NewBlockCount);
		NewHeader.TotalRawSize		  = NewTotalRawSize;
		NewHeader.TotalCompressedSize = NewTotalCompressedSize;
		NewHeader.RawHash			  = BLAKE3();
		NewHeader.Write(NewCompressedHeaderData.GetMutableView().Left(sizeof(BufferHeader) + NewMetaSize));

		return CompositeBuffer(NewCompressedHeaderData.MoveToShared(), NewCompressedData);
	}
}

CompositeBuffer
CopyCompressedRange(const BufferHeader&	   Header,
					MemoryView			   HeaderRawData,
					const CompositeBuffer& CompressedData,
					uint64_t			   RawOffset,
					uint64_t			   RawSize)
{
	if (Header.TotalRawSize < RawOffset + RawSize)
	{
		return CompositeBuffer();
	}

	if (Header.Method == CompressionMethod::None)
	{
		UniqueBuffer NewCompressedData = UniqueBuffer::Alloc(RawSize);
		CompressedData.CopyTo(NewCompressedData.GetMutableView(), sizeof(Header) + RawOffset);

		BufferHeader NewHeader		  = Header;
		NewHeader.Crc32				  = 0;
		NewHeader.TotalRawSize		  = RawSize;
		NewHeader.TotalCompressedSize = NewHeader.TotalRawSize + sizeof(BufferHeader);
		NewHeader.RawHash			  = BLAKE3();

		UniqueBuffer HeaderData = UniqueBuffer::Alloc(sizeof(BufferHeader));
		NewHeader.Write(HeaderData);

		return CompositeBuffer(HeaderData.MoveToShared(), NewCompressedData.MoveToShared());
	}
	else
	{
		MemoryView				  BlockSizeView = HeaderRawData.Mid(sizeof(Header), Header.BlockCount * sizeof(uint32_t));
		std::span<uint32_t const> CompressedBlockSizes(reinterpret_cast<const uint32_t*>(BlockSizeView.GetData()), Header.BlockCount);

		const uint64_t BlockSize		= uint64_t(1) << Header.BlockSizeExponent;
		const uint64_t LastBlockSize	= BlockSize - ((Header.BlockCount * BlockSize) - Header.TotalRawSize);
		const size_t   FirstBlock		= uint64_t(RawOffset / BlockSize);
		const size_t   LastBlock		= uint64_t((RawOffset + RawSize - 1) / BlockSize);
		uint64_t	   CompressedOffset = sizeof(BufferHeader) + uint64_t(Header.BlockCount) * sizeof(uint32_t);

		const uint64_t		  NewBlockCount		= LastBlock - FirstBlock + 1;
		const uint64_t		  NewMetaSize		= NewBlockCount * sizeof(uint32_t);
		uint64_t			  NewCompressedSize = 0;
		uint64_t			  NewTotalRawSize	= 0;
		std::vector<uint32_t> NewCompressedBlockSizes;

		NewCompressedBlockSizes.reserve(NewBlockCount);
		for (size_t BlockIndex = FirstBlock; BlockIndex <= LastBlock; ++BlockIndex)
		{
			const uint64_t UncompressedBlockSize = (BlockIndex == Header.BlockCount - 1) ? LastBlockSize : BlockSize;
			NewTotalRawSize += UncompressedBlockSize;

			const uint32_t CompressedBlockSize = CompressedBlockSizes[BlockIndex];
			NewCompressedBlockSizes.push_back(CompressedBlockSize);
			NewCompressedSize += ByteSwap(CompressedBlockSize);
		}

		const uint64_t	  NewTotalCompressedSize = sizeof(BufferHeader) + NewBlockCount * sizeof(uint32_t) + NewCompressedSize;
		UniqueBuffer	  NewCompressedData		 = UniqueBuffer::Alloc(NewTotalCompressedSize);
		MutableMemoryView NewCompressedBlocks	 = NewCompressedData.GetMutableView() + sizeof(BufferHeader) + NewMetaSize;

		// Seek to first compressed block
		for (size_t BlockIndex = 0; BlockIndex < FirstBlock; ++BlockIndex)
		{
			const uint64_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]);
			CompressedOffset += CompressedBlockSize;
		}

		// Copy blocks
		UniqueBuffer	 CompressedBlockCopy;
		const MemoryView CompressedRange = CompressedData.ViewOrCopyRange(CompressedOffset, NewCompressedSize, CompressedBlockCopy);
		NewCompressedBlocks.CopyFrom(CompressedRange);

		// Copy block sizes
		NewCompressedData.GetMutableView().Mid(sizeof(BufferHeader), NewMetaSize).CopyFrom(MakeMemoryView(NewCompressedBlockSizes));

		BufferHeader NewHeader;
		NewHeader.Crc32				  = 0;
		NewHeader.Method			  = Header.Method;
		NewHeader.Compressor		  = Header.Compressor;
		NewHeader.CompressionLevel	  = Header.CompressionLevel;
		NewHeader.BlockSizeExponent	  = Header.BlockSizeExponent;
		NewHeader.BlockCount		  = static_cast<uint32_t>(NewBlockCount);
		NewHeader.TotalRawSize		  = NewTotalRawSize;
		NewHeader.TotalCompressedSize = NewTotalCompressedSize;
		NewHeader.RawHash			  = BLAKE3();
		NewHeader.Write(NewCompressedData.GetMutableView().Left(sizeof(BufferHeader) + NewMetaSize));

		return CompositeBuffer(NewCompressedData.MoveToShared());
	}
}

}  // namespace zen::detail

namespace zen {

const CompressedBuffer CompressedBuffer::Null;

CompressedBuffer
CompressedBuffer::Compress(const CompositeBuffer& RawData,
						   OodleCompressor		  Compressor,
						   OodleCompressionLevel  CompressionLevel,
						   uint64_t				  BlockSize)
{
	using namespace detail;

	if (BlockSize == 0)
	{
		BlockSize = DefaultBlockSize;
	}

	CompressedBuffer Local;
	if (CompressionLevel == OodleCompressionLevel::None)
	{
		Local.CompressedData = NoneEncoder().Compress(RawData, BlockSize);
	}
	else
	{
		Local.CompressedData = OodleEncoder(Compressor, CompressionLevel).Compress(RawData, BlockSize);
	}
	return Local;
}

CompressedBuffer
CompressedBuffer::Compress(const SharedBuffer&	 RawData,
						   OodleCompressor		 Compressor,
						   OodleCompressionLevel CompressionLevel,
						   uint64_t				 BlockSize)
{
	return Compress(CompositeBuffer(RawData), Compressor, CompressionLevel, BlockSize);
}

CompressedBuffer
CompressedBuffer::FromCompressed(const CompositeBuffer& InCompressedData, IoHash& OutRawHash, uint64_t& OutRawSize)
{
	CompressedBuffer Local;
	Local.CompressedData = detail::ValidBufferOrEmpty(InCompressedData, OutRawHash, OutRawSize);
	return Local;
}

CompressedBuffer
CompressedBuffer::FromCompressed(CompositeBuffer&& InCompressedData, IoHash& OutRawHash, uint64_t& OutRawSize)
{
	CompressedBuffer Local;
	Local.CompressedData = detail::ValidBufferOrEmpty(std::move(InCompressedData), OutRawHash, OutRawSize);
	return Local;
}

CompressedBuffer
CompressedBuffer::FromCompressed(const SharedBuffer& InCompressedData, IoHash& OutRawHash, uint64_t& OutRawSize)
{
	CompressedBuffer Local;
	Local.CompressedData = detail::ValidBufferOrEmpty(InCompressedData, OutRawHash, OutRawSize);
	return Local;
}

CompressedBuffer
CompressedBuffer::FromCompressed(SharedBuffer&& InCompressedData, IoHash& OutRawHash, uint64_t& OutRawSize)
{
	CompressedBuffer Local;
	Local.CompressedData = detail::ValidBufferOrEmpty(std::move(InCompressedData), OutRawHash, OutRawSize);
	return Local;
}

CompressedBuffer
CompressedBuffer::FromCompressedNoValidate(IoBuffer&& InCompressedData)
{
	if (InCompressedData.GetSize() < sizeof(detail::BufferHeader))
	{
		return CompressedBuffer();
	}
	CompressedBuffer Local;
	Local.CompressedData = CompositeBuffer(SharedBuffer(std::move(InCompressedData)));
	return Local;
}

CompressedBuffer
CompressedBuffer::FromCompressedNoValidate(CompositeBuffer&& InCompressedData)
{
	if (InCompressedData.GetSize() < sizeof(detail::BufferHeader))
	{
		return CompressedBuffer();
	}
	CompressedBuffer Local;
	Local.CompressedData = std::move(InCompressedData);
	return Local;
}

bool
CompressedBuffer::ValidateCompressedHeader(IoBuffer&& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize)
{
	return detail::BufferHeader::IsValid(SharedBuffer(std::move(CompressedData)), OutRawHash, OutRawSize);
}

bool
CompressedBuffer::ValidateCompressedHeader(const IoBuffer& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize)
{
	return detail::BufferHeader::IsValid(SharedBuffer(CompressedData), OutRawHash, OutRawSize);
}

size_t
CompressedBuffer::GetHeaderSizeForNoneEncoder()
{
	return sizeof(detail::BufferHeader);
}

UniqueBuffer
CompressedBuffer::CreateHeaderForNoneEncoder(uint64_t RawSize, const BLAKE3& RawHash)
{
	detail::BufferHeader Header;
	Header.Method			   = detail::CompressionMethod::None;
	Header.BlockCount		   = 1;
	Header.TotalRawSize		   = RawSize;
	Header.TotalCompressedSize = Header.TotalRawSize + sizeof(detail::BufferHeader);
	Header.RawHash			   = RawHash;
	UniqueBuffer HeaderData	   = UniqueBuffer::Alloc(sizeof(detail::BufferHeader));
	Header.Write(HeaderData);
	return HeaderData;
}

uint64_t
CompressedBuffer::DecodeRawSize() const
{
	return CompressedData ? detail::BufferHeader::Read(CompressedData).TotalRawSize : 0;
}

IoHash
CompressedBuffer::DecodeRawHash() const
{
	return CompressedData ? IoHash::FromBLAKE3(detail::BufferHeader::Read(CompressedData).RawHash) : IoHash();
}

CompressedBuffer
CompressedBuffer::CopyRange(uint64_t RawOffset, uint64_t RawSize) const
{
	CompressedBuffer Range;
	if (RawSize > 0)
	{
		detail::BufferHeader Header;
		UniqueBuffer		 RawHeaderData;
		if (ReadHeader(CompressedData, Header, &RawHeaderData))
		{
			const uint64_t TotalRawSize = RawSize < ~uint64_t(0) ? RawSize : Header.TotalRawSize - RawOffset;
			Range.CompressedData		= CopyCompressedRange(Header, RawHeaderData.GetView(), CompressedData, RawOffset, TotalRawSize);
		}
	}
	return Range;
}

CompressedBuffer
CompressedBuffer::GetRange(uint64_t RawOffset, uint64_t RawSize) const
{
	CompressedBuffer Range;
	if (RawSize > 0)
	{
		detail::BufferHeader Header;
		UniqueBuffer		 RawHeaderData;
		if (ReadHeader(CompressedData, Header, &RawHeaderData))
		{
			const uint64_t TotalRawSize = RawSize < ~uint64_t(0) ? RawSize : Header.TotalRawSize - RawOffset;
			Range.CompressedData		= GetCompressedRange(Header, RawHeaderData.GetView(), CompressedData, RawOffset, TotalRawSize);
		}
	}
	return Range;
}

bool
CompressedBuffer::TryDecompressTo(MutableMemoryView RawView, uint64_t RawOffset) const
{
	using namespace detail;
	if (CompressedData && RawView.GetSize() > 0)
	{
		const BufferHeader Header = BufferHeader::Read(CompressedData);
		if (Header.Magic == BufferHeader::ExpectedMagic)
		{
			if (const BaseDecoder* const Decoder = GetDecoder(Header.Method))
			{
				return Decoder->TryDecompressTo(Header, CompressedData, RawView, RawOffset);
			}
		}
	}
	return false;
}

SharedBuffer
CompressedBuffer::Decompress(uint64_t RawOffset, uint64_t RawSize) const
{
	using namespace detail;
	if (CompressedData && RawSize > 0)
	{
		const BufferHeader Header = BufferHeader::Read(CompressedData);
		if (Header.Magic == BufferHeader::ExpectedMagic)
		{
			if (const BaseDecoder* const Decoder = GetDecoder(Header.Method))
			{
				const uint64_t TotalRawSize = RawSize < ~uint64_t(0) ? RawSize : Header.TotalRawSize - RawOffset;
				UniqueBuffer   RawData		= UniqueBuffer::Alloc(TotalRawSize);
				if (Decoder->TryDecompressTo(Header, CompressedData, RawData, RawOffset))
				{
					return RawData.MoveToShared();
				}
			}
		}
	}
	return SharedBuffer();
}

CompositeBuffer
CompressedBuffer::DecompressToComposite() const
{
	using namespace detail;
	if (CompressedData)
	{
		const BufferHeader Header = BufferHeader::Read(CompressedData);
		if (Header.Magic == BufferHeader::ExpectedMagic)
		{
			if (const BaseDecoder* const Decoder = GetDecoder(Header.Method))
			{
				return Decoder->DecompressToComposite(Header, CompressedData);
			}
		}
	}
	return CompositeBuffer();
}

bool
CompressedBuffer::TryGetCompressParameters(OodleCompressor&		  OutCompressor,
										   OodleCompressionLevel& OutCompressionLevel,
										   uint64_t&			  OutBlockSize) const
{
	using namespace detail;
	if (CompressedData)
	{
		switch (const BufferHeader Header = BufferHeader::Read(CompressedData); Header.Method)
		{
			case CompressionMethod::None:
				OutCompressor		= OodleCompressor::NotSet;
				OutCompressionLevel = OodleCompressionLevel::None;
				OutBlockSize		= 0;
				return true;
			case CompressionMethod::Oodle:
				OutCompressor		= OodleCompressor(Header.Compressor);
				OutCompressionLevel = OodleCompressionLevel(Header.CompressionLevel);
				OutBlockSize		= uint64_t(1) << Header.BlockSizeExponent;
				return true;
			default:
				break;
		}
	}
	return false;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

CompressedBufferReader::CompressedBufferReader(Archive& Archive)
{
	SetSource(Archive);
}

CompressedBufferReader::CompressedBufferReader(const CompressedBuffer& Buffer)
{
	SetSource(Buffer);
}

void
CompressedBufferReader::ResetBuffers()
{
	using namespace detail;
	if (SourceArchive && Context.HeaderOffset != MAX_uint64)
	{
		SourceArchive->Seek(int64_t(Context.HeaderOffset));
	}
	Context = DecoderContext();
}

void
CompressedBufferReader::ResetSource()
{
	Context.HeaderOffset = MAX_uint64;
	SourceArchive		 = nullptr;
	SourceBuffer		 = nullptr;
}

void
CompressedBufferReader::SetSource(Archive& Archive)
{
	if (SourceArchive == &Archive)
	{
		return;
	}
	Context.HeaderOffset = MAX_uint64;
	SourceArchive		 = &Archive;
	SourceBuffer		 = nullptr;
}

void
CompressedBufferReader::SetSource(const CompressedBuffer& Buffer)
{
	if (SourceBuffer == &Buffer)
	{
		return;
	}
	Context.HeaderOffset = MAX_uint64;
	SourceArchive		 = nullptr;
	SourceBuffer		 = &Buffer;
}

uint64_t
CompressedBufferReader::GetCompressedSize()
{
	using namespace detail;
	BufferHeader Header;
	MemoryView	 HeaderView;
	if (TryReadHeader(Header, HeaderView))
	{
		return Header.TotalCompressedSize;
	}
	return 0;
}

uint64_t
CompressedBufferReader::GetRawSize()
{
	using namespace detail;
	BufferHeader Header;
	MemoryView	 HeaderView;
	if (TryReadHeader(Header, HeaderView))
	{
		return Header.TotalRawSize;
	}
	return 0;
}

IoHash
CompressedBufferReader::GetRawHash()
{
	using namespace detail;
	BufferHeader Header;
	MemoryView	 HeaderView;
	if (TryReadHeader(Header, HeaderView))
	{
		return IoHash::FromBLAKE3(Header.RawHash);
	}
	return {};
}

bool
CompressedBufferReader::TryGetCompressParameters(OodleCompressor&		OutCompressor,
												 OodleCompressionLevel& OutCompressionLevel,
												 uint64_t&				OutBlockSize)
{
	using namespace detail;
	BufferHeader Header;
	MemoryView	 HeaderView;
	if (TryReadHeader(Header, HeaderView))
	{
		return Header.TryGetCompressParameters(OutCompressor, OutCompressionLevel, OutBlockSize);
	}
	return false;
}

bool
CompressedBufferReader::TryDecompressTo(const MutableMemoryView RawView, const uint64_t RawOffset)
{
	using namespace detail;
	BufferHeader Header;
	MemoryView	 HeaderView;
	if (TryReadHeader(Header, HeaderView))
	{
		const uint64_t TotalRawSize = Header.TotalRawSize;
		if (RawOffset <= TotalRawSize && RawView.GetSize() <= TotalRawSize - RawOffset)
		{
			if (const BaseDecoder* const Decoder = GetDecoder(Header.Method))
			{
				if (Decoder->TryDecompressTo(
						Context,
						SourceArchive ? static_cast<const DecoderSource&>(ArchiveDecoderSource(*SourceArchive, Context.HeaderOffset))
									  : static_cast<const DecoderSource&>(BufferDecoderSource(SourceBuffer->GetCompressed())),
						Header,
						HeaderView,
						RawOffset,
						RawView))
				{
					return true;
				}
			}
		}
	}
	return false;
}

SharedBuffer
CompressedBufferReader::Decompress(const uint64_t RawOffset, const uint64_t RawSize)
{
	if (RawSize > 0)
	{
		using namespace detail;
		BufferHeader Header;
		MemoryView	 HeaderView;
		if (TryReadHeader(Header, HeaderView))
		{
			const uint64_t TotalRawSize	 = Header.TotalRawSize;
			const uint64_t RawSizeToCopy = RawSize == MAX_uint64 ? TotalRawSize - RawOffset : RawSize;
			if (RawOffset <= TotalRawSize && RawSizeToCopy <= TotalRawSize - RawOffset)
			{
				if (const BaseDecoder* const Decoder = GetDecoder(Header.Method))
				{
					UniqueBuffer RawData = UniqueBuffer::Alloc(RawSizeToCopy);
					if (Decoder->TryDecompressTo(
							Context,
							SourceArchive ? static_cast<const DecoderSource&>(ArchiveDecoderSource(*SourceArchive, Context.HeaderOffset))
										  : static_cast<const DecoderSource&>(BufferDecoderSource(SourceBuffer->GetCompressed())),
							Header,
							HeaderView,
							RawOffset,
							RawData))
					{
						return RawData.MoveToShared();
					}
				}
			}
		}
	}
	return {};
}

CompositeBuffer
CompressedBufferReader::DecompressToComposite(const uint64_t RawOffset, const uint64_t RawSize)
{
	using namespace detail;
	BufferHeader Header;
	MemoryView	 HeaderView;
	if (TryReadHeader(Header, HeaderView))
	{
		const uint64_t TotalRawSize	 = Header.TotalRawSize;
		const uint64_t RawSizeToCopy = RawSize == MAX_uint64 ? TotalRawSize - RawOffset : RawSize;
		if (RawOffset <= TotalRawSize && RawSizeToCopy <= TotalRawSize - RawOffset)
		{
			if (const BaseDecoder* const Decoder = GetDecoder(Header.Method))
			{
				return Decoder->DecompressToComposite(
					Context,
					SourceArchive ? static_cast<const DecoderSource&>(ArchiveDecoderSource(*SourceArchive, Context.HeaderOffset))
								  : static_cast<const DecoderSource&>(BufferDecoderSource(SourceBuffer->GetCompressed())),
					Header,
					HeaderView,
					RawOffset,
					RawSizeToCopy);
			}
		}
	}
	return CompositeBuffer();
}

bool
CompressedBufferReader::TryReadHeader(detail::BufferHeader& OutHeader, MemoryView& OutHeaderView)
{
	using namespace detail;
	if (Archive* const Archive = SourceArchive)
	{
		return detail::TryReadHeader(Context, *Archive, OutHeader, OutHeaderView);
	}
	if (const CompressedBuffer* const Buffer = SourceBuffer)
	{
		return detail::TryReadHeader(Context, Buffer->GetCompressed(), OutHeader, OutHeaderView);
	}
	return false;
}

/** A type that sets the source of a reader upon construction and resets it upon destruction. */
class CompressedBufferReaderSourceScope
{
public:
	inline CompressedBufferReaderSourceScope(CompressedBufferReader& InReader, Archive& InArchive) : Reader(InReader)
	{
		Reader.SetSource(InArchive);
	}

	inline CompressedBufferReaderSourceScope(CompressedBufferReader& InReader, const CompressedBuffer& InBuffer) : Reader(InReader)
	{
		Reader.SetSource(InBuffer);
	}

	inline ~CompressedBufferReaderSourceScope() { Reader.ResetSource(); }

private:
	CompressedBufferReader& Reader;
};

/**
	______________________ _____________________________
	\__    ___/\_   _____//   _____/\__    ___/   _____/
	  |    |    |    __)_ \_____  \   |    |  \_____  \
	  |    |    |        \/        \  |    |  /        \
	  |____|   /_______  /_______  /  |____| /_______  /
					   \/        \/                  \/
 */

#if ZEN_WITH_TESTS

TEST_CASE("CompressedBuffer")
{
	uint8_t Zeroes[1024]{};
	uint8_t Ones[1024];
	memset(Ones, 1, sizeof Ones);

	{
		CompressedBuffer Buffer = CompressedBuffer::Compress(CompositeBuffer(SharedBuffer::MakeView(MakeMemoryView(Zeroes))),
															 OodleCompressor::NotSet,
															 OodleCompressionLevel::None);

		CHECK(Buffer.DecodeRawSize() == sizeof(Zeroes));
		CHECK(Buffer.GetCompressedSize() == (sizeof(Zeroes) + sizeof(detail::BufferHeader)));

		CompositeBuffer	 Compressed = Buffer.GetCompressed();
		IoHash			 DecodedHash;
		uint64_t		 DecodedRawSize;
		CompressedBuffer BufferD = CompressedBuffer::FromCompressed(Compressed, DecodedHash, DecodedRawSize);

		CHECK(BufferD.IsNull() == false);

		CompositeBuffer Decomp = BufferD.DecompressToComposite();

		CHECK(Decomp.GetSize() == DecodedRawSize);
		CHECK(IoHash::HashBuffer(Decomp) == DecodedHash);
	}

	{
		CompressedBuffer Buffer = CompressedBuffer::Compress(
			CompositeBuffer(SharedBuffer::MakeView(MakeMemoryView(Zeroes)), SharedBuffer::MakeView(MakeMemoryView(Ones))),
			OodleCompressor::NotSet,
			OodleCompressionLevel::None);

		CHECK(Buffer.DecodeRawSize() == (sizeof(Zeroes) + sizeof(Ones)));
		CHECK(Buffer.GetCompressedSize() == (sizeof(Zeroes) + sizeof(Ones) + sizeof(detail::BufferHeader)));

		CompositeBuffer	 Compressed = Buffer.GetCompressed();
		IoHash			 DecodedHash;
		uint64_t		 DecodedRawSize;
		CompressedBuffer BufferD = CompressedBuffer::FromCompressed(Compressed, DecodedHash, DecodedRawSize);

		CHECK(BufferD.IsNull() == false);

		CompositeBuffer Decomp = BufferD.DecompressToComposite();

		CHECK(Decomp.GetSize() == DecodedRawSize);
		CHECK(IoHash::HashBuffer(Decomp) == DecodedHash);
	}

	{
		CompressedBuffer Buffer = CompressedBuffer::Compress(CompositeBuffer(SharedBuffer::MakeView(MakeMemoryView(Zeroes))));

		CHECK(Buffer.DecodeRawSize() == sizeof(Zeroes));
		CHECK(Buffer.GetCompressedSize() < sizeof(Zeroes));

		CompositeBuffer	 Compressed = Buffer.GetCompressed();
		IoHash			 DecodedHash;
		uint64_t		 DecodedRawSize;
		CompressedBuffer BufferD = CompressedBuffer::FromCompressed(Compressed, DecodedHash, DecodedRawSize);

		CHECK(BufferD.IsNull() == false);

		CompositeBuffer Decomp = BufferD.DecompressToComposite();

		CHECK(Decomp.GetSize() == DecodedRawSize);
		CHECK(IoHash::HashBuffer(Decomp) == DecodedHash);
	}

	{
		CompressedBuffer Buffer = CompressedBuffer::Compress(
			CompositeBuffer(SharedBuffer::MakeView(MakeMemoryView(Zeroes)), SharedBuffer::MakeView(MakeMemoryView(Ones))));

		CHECK(Buffer.DecodeRawSize() == (sizeof(Zeroes) + sizeof(Ones)));
		CHECK(Buffer.GetCompressedSize() < (sizeof(Zeroes) + sizeof(Ones)));

		CompositeBuffer	 Compressed = Buffer.GetCompressed();
		IoHash			 DecodedHash;
		uint64_t		 DecodedRawSize;
		CompressedBuffer BufferD = CompressedBuffer::FromCompressed(Compressed, DecodedHash, DecodedRawSize);

		CHECK(BufferD.IsNull() == false);

		CompositeBuffer Decomp = BufferD.DecompressToComposite();

		CHECK(Decomp.GetSize() == DecodedRawSize);
		CHECK(IoHash::HashBuffer(Decomp) == DecodedHash);
	}

	auto GenerateData = [](uint64_t N) -> std::vector<uint64_t> {
		std::vector<uint64_t> Data;
		Data.resize(N);
		for (size_t Idx = 0; Idx < Data.size(); ++Idx)
		{
			Data[Idx] = Idx;
		}
		return Data;
	};

	auto ValidateData = [](std::span<uint64_t const> Values, std::span<uint64_t const> ExpectedValues, uint64_t Offset) {
		for (size_t Idx = Offset; uint64_t Value : Values)
		{
			const uint64_t ExpectedValue = ExpectedValues[Idx++];
			CHECK(Value == ExpectedValue);
		}
	};

	SUBCASE("decompress with offset and size")
	{
		auto UncompressAndValidate = [&ValidateData](CompressedBuffer			  Compressed,
													 uint64_t					  OffsetCount,
													 uint64_t					  Count,
													 const std::vector<uint64_t>& ExpectedValues) {
			SharedBuffer Uncompressed = Compressed.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
			CHECK(Uncompressed.GetSize() == Count * sizeof(uint64_t));
			std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
			ValidateData(Values, ExpectedValues, OffsetCount);
		};

		const uint64_t		  BlockSize		 = 64 * sizeof(uint64_t);
		const uint64_t		  N				 = 5000;
		std::vector<uint64_t> ExpectedValues = GenerateData(N);
		CompressedBuffer	  Compressed	 = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
																  OodleCompressor::Mermaid,
																  OodleCompressionLevel::Optimal4,
																  BlockSize);
		UncompressAndValidate(Compressed, 0, N, ExpectedValues);
		UncompressAndValidate(Compressed, 1, N - 1, ExpectedValues);
		UncompressAndValidate(Compressed, N - 1, 1, ExpectedValues);
		UncompressAndValidate(Compressed, 0, 1, ExpectedValues);
		UncompressAndValidate(Compressed, 2, 4, ExpectedValues);
		UncompressAndValidate(Compressed, 0, 512, ExpectedValues);
		UncompressAndValidate(Compressed, 3, 514, ExpectedValues);
		UncompressAndValidate(Compressed, 256, 512, ExpectedValues);
		UncompressAndValidate(Compressed, 512, 512, ExpectedValues);
	}

	SUBCASE("decompress with offset only")
	{
		const uint64_t			  BlockSize		 = 64 * sizeof(uint64_t);
		const uint64_t			  N				 = 1000;
		std::vector<uint64_t>	  ExpectedValues = GenerateData(N);
		CompressedBuffer		  Compressed	 = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
																  OodleCompressor::Mermaid,
																  OodleCompressionLevel::Optimal4,
																  BlockSize);
		const uint64_t			  OffsetCount	 = 150;
		SharedBuffer			  Uncompressed	 = Compressed.Decompress(OffsetCount * sizeof(uint64_t));
		std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
		ValidateData(Values, ExpectedValues, OffsetCount);
	}

	SUBCASE("decompress buffer with one block")
	{
		const uint64_t			  BlockSize		 = 256 * sizeof(uint64_t);
		const uint64_t			  N				 = 100;
		std::vector<uint64_t>	  ExpectedValues = GenerateData(N);
		CompressedBuffer		  Compressed	 = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
																  OodleCompressor::Mermaid,
																  OodleCompressionLevel::Optimal4,
																  BlockSize);
		const uint64_t			  OffsetCount	 = 2;
		const uint64_t			  Count			 = 50;
		SharedBuffer			  Uncompressed	 = Compressed.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
		std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
		ValidateData(Values, ExpectedValues, OffsetCount);
	}

	SUBCASE("decompress uncompressed buffer")
	{
		const uint64_t		  N				 = 4242;
		std::vector<uint64_t> ExpectedValues = GenerateData(N);
		CompressedBuffer	  Compressed	 = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
																  OodleCompressor::NotSet,
																  OodleCompressionLevel::None);
		{
			const uint64_t			  OffsetCount  = 0;
			const uint64_t			  Count		   = N;
			SharedBuffer			  Uncompressed = Compressed.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
			std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
			ValidateData(Values, ExpectedValues, OffsetCount);
		}

		{
			const uint64_t			  OffsetCount  = 21;
			const uint64_t			  Count		   = 999;
			SharedBuffer			  Uncompressed = Compressed.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
			std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
			ValidateData(Values, ExpectedValues, OffsetCount);
		}
	}

	SUBCASE("copy range")
	{
		const uint64_t		  BlockSize		 = 64 * sizeof(uint64_t);
		const uint64_t		  N				 = 1000;
		std::vector<uint64_t> ExpectedValues = GenerateData(N);

		CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
																 OodleCompressor::Mermaid,
																 OodleCompressionLevel::Optimal4,
																 BlockSize);

		{
			const uint64_t OffsetCount	= 0;
			const uint64_t Count		= N;
			SharedBuffer   Uncompressed = Compressed.CopyRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress();
			std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}

		{
			const uint64_t OffsetCount	= 64;
			const uint64_t Count		= N - 64;
			SharedBuffer   Uncompressed = Compressed.CopyRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress();
			std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}

		{
			const uint64_t OffsetCount		= 64 * 2 + 32;
			const uint64_t Count			= N - OffsetCount;
			const uint64_t RawOffset		= OffsetCount * sizeof(uint64_t);
			const uint64_t RawSize			= Count * sizeof(uint64_t);
			uint64_t	   FirstBlockOffset = RawOffset % BlockSize;

			SharedBuffer			  Uncompressed = Compressed.CopyRange(RawOffset, RawSize).Decompress();
			std::span<uint64_t const> AllValues((const uint64_t*)Uncompressed.GetData(), RawSize / sizeof(uint64_t));
			std::span<uint64_t const> Values((const uint64_t*)(((const uint8_t*)(Uncompressed.GetData()) + FirstBlockOffset)),
											 RawSize / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}

		{
			const uint64_t OffsetCount		= 64 * 2 + 63;
			const uint64_t Count			= N - OffsetCount - 5;
			const uint64_t RawOffset		= OffsetCount * sizeof(uint64_t);
			const uint64_t RawSize			= Count * sizeof(uint64_t);
			uint64_t	   FirstBlockOffset = RawOffset % BlockSize;

			SharedBuffer			  Uncompressed = Compressed.CopyRange(RawOffset, RawSize).Decompress();
			std::span<uint64_t const> AllValues((const uint64_t*)Uncompressed.GetData(), RawSize / sizeof(uint64_t));
			std::span<uint64_t const> Values((const uint64_t*)(((const uint8_t*)(Uncompressed.GetData()) + FirstBlockOffset)),
											 RawSize / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}
	}

	SUBCASE("get range")
	{
		const uint64_t		  BlockSize		 = 64 * sizeof(uint64_t);
		const uint64_t		  N				 = 1000;
		std::vector<uint64_t> ExpectedValues = GenerateData(N);

		CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
																 OodleCompressor::Mermaid,
																 OodleCompressionLevel::Optimal4,
																 BlockSize);

		{
			const uint64_t OffsetCount	= 0;
			const uint64_t Count		= N;
			SharedBuffer   Uncompressed = Compressed.GetRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress();
			std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}

		{
			const uint64_t OffsetCount	= 64;
			const uint64_t Count		= N - 64;
			SharedBuffer   Uncompressed = Compressed.GetRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress();
			std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}

		{
			const uint64_t OffsetCount		= 64 * 2 + 32;
			const uint64_t Count			= N - OffsetCount;
			const uint64_t RawOffset		= OffsetCount * sizeof(uint64_t);
			const uint64_t RawSize			= Count * sizeof(uint64_t);
			uint64_t	   FirstBlockOffset = RawOffset % BlockSize;

			SharedBuffer			  Uncompressed = Compressed.GetRange(RawOffset, RawSize).Decompress();
			std::span<uint64_t const> AllValues((const uint64_t*)Uncompressed.GetData(), RawSize / sizeof(uint64_t));
			std::span<uint64_t const> Values((const uint64_t*)(((const uint8_t*)(Uncompressed.GetData()) + FirstBlockOffset)),
											 RawSize / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}

		{
			const uint64_t OffsetCount		= 64 * 2 + 63;
			const uint64_t Count			= N - OffsetCount - 5;
			const uint64_t RawOffset		= OffsetCount * sizeof(uint64_t);
			const uint64_t RawSize			= Count * sizeof(uint64_t);
			uint64_t	   FirstBlockOffset = RawOffset % BlockSize;

			SharedBuffer			  Uncompressed = Compressed.GetRange(RawOffset, RawSize).Decompress();
			std::span<uint64_t const> AllValues((const uint64_t*)Uncompressed.GetData(), RawSize / sizeof(uint64_t));
			std::span<uint64_t const> Values((const uint64_t*)(((const uint8_t*)(Uncompressed.GetData()) + FirstBlockOffset)),
											 RawSize / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}
	}

	SUBCASE("copy uncompressed range")
	{
		const uint64_t		  N				 = 1000;
		std::vector<uint64_t> ExpectedValues = GenerateData(N);

		CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
																 OodleCompressor::NotSet,
																 OodleCompressionLevel::None);

		{
			const uint64_t OffsetCount	= 0;
			const uint64_t Count		= N;
			SharedBuffer   Uncompressed = Compressed.CopyRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress();
			std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}

		{
			const uint64_t OffsetCount	= 1;
			const uint64_t Count		= N - OffsetCount;
			SharedBuffer   Uncompressed = Compressed.CopyRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress();
			std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}

		{
			const uint64_t OffsetCount	= 42;
			const uint64_t Count		= 100;
			SharedBuffer   Uncompressed = Compressed.CopyRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress();
			std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}
	}

	SUBCASE("get uncompressed range")
	{
		const uint64_t		  N				 = 1000;
		std::vector<uint64_t> ExpectedValues = GenerateData(N);

		CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
																 OodleCompressor::NotSet,
																 OodleCompressionLevel::None);

		{
			const uint64_t OffsetCount	= 0;
			const uint64_t Count		= N;
			SharedBuffer   Uncompressed = Compressed.GetRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress();
			std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}

		{
			const uint64_t OffsetCount	= 1;
			const uint64_t Count		= N - OffsetCount;
			SharedBuffer   Uncompressed = Compressed.GetRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress();
			std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}

		{
			const uint64_t OffsetCount	= 42;
			const uint64_t Count		= 100;
			SharedBuffer   Uncompressed = Compressed.GetRange(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t)).Decompress();
			std::span<uint64_t const> Values((const uint64_t*)Uncompressed.GetData(), Uncompressed.GetSize() / sizeof(uint64_t));
			CHECK(Values.size() == Count);
			ValidateData(Values, ExpectedValues, OffsetCount);
		}
	}
}

TEST_CASE("CompressedBufferReader")
{
	const auto GenerateData = [](size_t N) -> std::vector<uint64_t> {
		std::vector<uint64_t> Data;
		Data.resize(N);
		for (size_t Idx = 0; Idx < Data.size(); ++Idx)
		{
			Data[Idx] = uint64_t(Idx);
		}
		return Data;
	};

	CompressedBufferReader Reader;

	SUBCASE("Decompress with offset and size")
	{
		const auto UncompressAndValidate = [&](const CompressedBuffer&		Compressed,
											   const int32_t				OffsetCount,
											   const int32_t				Count,
											   const std::vector<uint64_t>& ExpectedValues) {
			Reader.SetSource(Compressed);
			{
				const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
				CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count))));
			}
			{
				UniqueBuffer Uncompressed = UniqueBuffer::Alloc(Count * sizeof(uint64_t));
				if (Reader.TryDecompressTo(Uncompressed, OffsetCount * sizeof(uint64_t)))
				{
					CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count))));
				}
			}
		};

		constexpr uint64_t			BlockSize	   = 64 * sizeof(uint64_t);
		constexpr int32_t			N			   = 5000;
		const std::vector<uint64_t> ExpectedValues = GenerateData(N);

		const CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
																	   OodleCompressor::Mermaid,
																	   OodleCompressionLevel::Fast,
																	   BlockSize);

		UncompressAndValidate(Compressed, 0, N, ExpectedValues);
		UncompressAndValidate(Compressed, 1, N - 1, ExpectedValues);
		UncompressAndValidate(Compressed, N - 1, 1, ExpectedValues);
		UncompressAndValidate(Compressed, 0, 1, ExpectedValues);
		UncompressAndValidate(Compressed, 2, 4, ExpectedValues);
		UncompressAndValidate(Compressed, 0, 512, ExpectedValues);
		UncompressAndValidate(Compressed, 3, 514, ExpectedValues);
		UncompressAndValidate(Compressed, 256, 512, ExpectedValues);
		UncompressAndValidate(Compressed, 512, 512, ExpectedValues);
		UncompressAndValidate(Compressed, 512, 512, ExpectedValues);
		UncompressAndValidate(Compressed, 4993, 4, ExpectedValues);
	}

	SUBCASE("Decompress with offset only")
	{
		constexpr uint64_t			BlockSize	   = 64 * sizeof(uint64_t);
		constexpr int32_t			N			   = 1000;
		const std::vector<uint64_t> ExpectedValues = GenerateData(N);

		const CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
																	   OodleCompressor::Mermaid,
																	   OodleCompressionLevel::Fast,
																	   BlockSize);

		constexpr uint64_t OffsetCount = 150;
		{
			SharedBuffer					  Buffer = Compressed.GetCompressed().ToShared();
			BufferReader					  Ar(Buffer.GetData(), int64_t(Buffer.GetSize()));
			CompressedBufferReaderSourceScope Source(Reader, Ar);
			const SharedBuffer				  Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t));
			CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount))));
		}
		{
			CompressedBufferReaderSourceScope Source(Reader, Compressed);
			const SharedBuffer				  Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t));
			CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount))));
		}

		// Short (malformed) Buffer
		{
			IoHash				   RawHash;
			uint64_t			   RawSize = 0;
			const CompressedBuffer CompressedShort =
				CompressedBuffer::FromCompressed(Compressed.GetCompressed().Mid(0, Compressed.GetCompressedSize() - 128),
												 /* out */ RawHash,
												 /* out */ RawSize);
			Reader.SetSource(CompressedShort);
			const SharedBuffer Uncompressed = Reader.Decompress();
			CHECK(Uncompressed.IsNull());
		}
	}

	SUBCASE("Decompress only block")
	{
		constexpr uint64_t			BlockSize	   = 256 * sizeof(uint64_t);
		constexpr int32_t			N			   = 100;
		const std::vector<uint64_t> ExpectedValues = GenerateData(N);

		const CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
																	   OodleCompressor::Mermaid,
																	   OodleCompressionLevel::Fast,
																	   BlockSize);

		constexpr uint64_t OffsetCount = 2;
		constexpr uint64_t Count	   = 50;
		{
			SharedBuffer					  Buffer = Compressed.GetCompressed().ToShared();
			BufferReader					  Ar(Buffer.GetData(), int64_t(Buffer.GetSize()));
			CompressedBufferReaderSourceScope Source(Reader, Ar);
			const SharedBuffer				  Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
			CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count))));
		}
		{
			CompressedBufferReaderSourceScope Source(Reader, Compressed);
			const SharedBuffer				  Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
			CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count))));
		}
	}

	SUBCASE("Decompress from an uncompressed buffer.")
	{
		constexpr int32_t			N			   = 4242;
		const std::vector<uint64_t> ExpectedValues = GenerateData(N);

		const CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
																	   OodleCompressor::NotSet,
																	   OodleCompressionLevel::None);
		Reader.SetSource(Compressed);

		{
			constexpr uint64_t OffsetCount	= 0;
			constexpr uint64_t Count		= N;
			const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
			CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count))));
		}

		{
			constexpr uint64_t OffsetCount	= 21;
			constexpr uint64_t Count		= 999;
			const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
			CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count))));
		}

		// Short (malformed) Buffer
		{
			IoHash				   RawHash;
			uint64_t			   RawSize = 0;
			const CompressedBuffer CompressedShort =
				CompressedBuffer::FromCompressed(Compressed.GetCompressed().Mid(0, Compressed.GetCompressedSize() - 128),
												 /* out */ RawHash,
												 /* out */ RawSize);
			Reader.SetSource(CompressedShort);
			const SharedBuffer Uncompressed = Reader.Decompress();
			CHECK(Uncompressed.IsNull());
		}
	}
}

void
compress_forcelink()
{
}
#endif

}  // namespace zen
